﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;    // needed for Description Attributes/PropertyGrid
using System.Xml.Serialization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Security.Cryptography;
using System.Linq;
using System.Runtime.Serialization;

namespace ContinuumSL.Classes
{
    public class Projector
    {
        #region Properties / Member Variables

        private string _UniverseName = "";
        [Description("The name which the Universe")]
        public string UniverseName { get { return _UniverseName; } set { _UniverseName = value; } }

        private DateTime _InceptionDate = DateTime.Now;
        [Description("The Initial Date this Universe was created")]
        public DateTime InceptionDate { get { return _InceptionDate; } set { _InceptionDate = value; } }

        #region Metadata Properties for managing Simulations

        private bool _RunSimulation = false;
        [Description("Automatically run and update simulation daily")]
        public bool RunSimulation { get { return _RunSimulation; } set { _RunSimulation = value; } }

        private bool _EnableNotifications = false;
        [Description("Whether to notify daily when actor activity occurs")]
        public bool EnableNotifications { get { return _EnableNotifications; } set { _EnableNotifications = value; } }

        private int _pastWindow = 4;    
        [Description("The number of months in the past to render by default")]
        public int PastWindow { get { return _pastWindow; } set { _pastWindow = value; } }

        private int _futureWindow = 6;    // By default render one month of history
        [Description("The number of months in the future to render by default")]
        public int FutureWindow { get { return _futureWindow; } set { _futureWindow = value; } }

        #endregion

        public List<Actor> Actors = new List<Actor>();
        public List<Fund> Funds = new List<Fund>();

        public string Notes = "Enter your own notes here";

        private int _UpcomingDays = 10;
        [Description("The number of days to show in Upcoming Activity area")]
        public int UpcomingDays { get { return _UpcomingDays; } set { _UpcomingDays = value; } }

        private bool _AutoRefreshMarket = false;
        [Description("Whether market quotes will be automatically fetched")]
        public bool AutoRefreshMarket { get { return _AutoRefreshMarket; } set { _AutoRefreshMarket = value; } }

        private int _AutoRefreshRate = 3;
        [Description("The number of days between automatic quote retrievals")]
        public int AutoRefreshRate { get { return _AutoRefreshRate; } set { _AutoRefreshRate = value; } }

        // Volatile Actors are currently being used to post 0 amount transactions
        // So that projection start date, projection end date, and current date can
        // be shown in fund activity graph
        private List<Actor> VolatileActors = new List<Actor>();

        [XmlIgnore]
        public List<Activity> Activities = new List<Activity>();

        #endregion

        #region Constructors

        public Projector()
        {
        }

        public Projector(string UniverseName)
        {
            this.UniverseName = UniverseName;
        }

        #endregion

        #region Save/Load Universe

        public static Projector LoadUniverse(string Filename)
        {
			var store = IsolatedStorageFile.GetUserStoreForApplication();

			if (!store.FileExists(Filename)) return null;
			
            Projector newSettings;

            XmlSerializer s = new XmlSerializer(typeof(Projector));
            //TextReader r = new StreamReader(Filename);
			IsolatedStorageFileStream isfs = store.OpenFile(Filename, FileMode.Open);
            newSettings = (Projector)s.Deserialize(isfs);
            isfs.Close();

            return newSettings;
        }

        public static Projector LoadUniverse(Stream stream)
        {
            Projector newSettings;

            XmlSerializer s = new XmlSerializer(typeof(Projector));
            newSettings = (Projector)s.Deserialize(stream);
            stream.Close();

            return newSettings;
        }

        // Encryption is derived from samples here :
        // http://www.christec.co.nz/blog/archives/81
        // http://www.discussweb.com/other-web-programming-languages/2091-uses-symmetric-key-algorithm-rijndael-encrypt-decrypt-data.html
        // http://forums.silverlight.net/p/14449/220718.aspx
        public static Projector LoadUniverse(string Filename, string Password)
        {
            var store = IsolatedStorageFile.GetUserStoreForApplication();
            if (!store.FileExists(Filename)) return null;

            Projector newSettings;
            XmlSerializer s = new XmlSerializer(typeof(Projector));

            //byte[] rgbIV = System.Text.UTF8Encoding.UTF8.GetBytes("1150845670772664");
            byte[] saltValueBytes = System.Text.UTF8Encoding.UTF8.GetBytes("s0d1uMv4l");

            // Configure the desired encryption algorithm parameters
            AesManaged aes = new System.Security.Cryptography.AesManaged(); 

            Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(Password, saltValueBytes);

            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;

            aes.Key = rfc.GetBytes(aes.KeySize / 8);
            aes.IV = rfc.GetBytes(aes.BlockSize / 8);

            // Decryption example
            ICryptoTransform transform = aes.CreateDecryptor();
            using (IsolatedStorageFileStream inputStream = store.OpenFile(Filename, FileMode.Open))
            {
                // Wrap the input stream up with a CryptoStream
                // which performs the data decryption
                using (Stream cryptoStream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read))
                {
                    // Read data into the cryptoStream (which will fetch encrypted
                    // data from the inputStream and then decrypt it before returning
                    // it to us)
                    using (TextReader tr = new StreamReader(cryptoStream))
                        newSettings = (Projector)s.Deserialize(tr);
                }
            }

            return newSettings;
        }

        public bool SaveUniverse(Stream stream)
        {
            XmlSerializer s = new XmlSerializer(this.GetType());
            s.Serialize(stream, this);
            stream.Flush();
            stream.Close();
            return true;
        }

        public string SaveUniverse(string Filename)
        {
			var store = IsolatedStorageFile.GetUserStoreForApplication();
			
            XmlSerializer s = new XmlSerializer(this.GetType());
			IsolatedStorageFileStream isfs = store.OpenFile(Filename, FileMode.Create);
            s.Serialize(isfs, this);
            isfs.Close();

            return Filename;
        }

        // Encryption is derived from samples here :
        // http://www.christec.co.nz/blog/archives/81
        // http://www.discussweb.com/other-web-programming-languages/2091-uses-symmetric-key-algorithm-rijndael-encrypt-decrypt-data.html
        // http://forums.silverlight.net/p/14449/220718.aspx
        public string SaveUniverse(string Filename, string Password)
        {
            var store = IsolatedStorageFile.GetUserStoreForApplication();

            XmlSerializer s = new XmlSerializer(this.GetType());

            byte[] saltValueBytes = UTF8Encoding.UTF8.GetBytes("s0d1uMv4l");

            // Configure the desired encryption algorithm parameters
            AesManaged aes = new AesManaged();

            Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(Password, saltValueBytes);

            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;

            aes.Key = rfc.GetBytes(aes.KeySize / 8);
            aes.IV = rfc.GetBytes(aes.BlockSize / 8);

            // Encryption Example
            ICryptoTransform transform = aes.CreateEncryptor();
            using (IsolatedStorageFileStream outputStream = store.OpenFile(Filename, FileMode.Create))
            {
              // Wrap the output stream up with a CryptoStream
              // which performs the data encryption
              using (Stream cryptoStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write))
              {
                // Store data into the cryptoStream (which will encrypt it
                // and then pass it along to our outputStream for storage)
                  using (TextWriter tw = new StreamWriter(cryptoStream))
                  {
                      s.Serialize(tw, this);
                  }
              }
            }

            return Filename;
        }

        #endregion

        #region Projection Management

        public void ResetVolatile()
        {
            foreach (Fund fund in Funds) fund.ResetVolatile();
            VolatileActors.Clear();
            Activities.Clear();
        }

        // RenderUniverse : runs the projection 
        //
        // End result is that the Projector.Activities list is constructed with 
        //    all intermediary fund balances stored within each activity
        //
        // Each Funds VolatileBalances and VolatileTransactions vectors are filled
        //    for later analysis
        public void RenderUniverse(DateTime StartDate, DateTime EndDate) 
        {
            //ResetVolatile();

            // Compile list of all activities to be carried out
            foreach (Actor actor in Actors)
            {
                if (!actor.IsDisabled)
                   Activities.AddRange(actor.ProjectActivities(StartDate, EndDate));
            }

            foreach (Fund f in Funds)
            {
                Activities.AddRange(f.ProjectActivities(StartDate, EndDate));
            }

            foreach (Actor actor in VolatileActors)
            {
                Activities.AddRange(actor.ProjectActivities(StartDate, EndDate));
            }

            // Sequence activities chronologically
            Activities.Sort(new ActivityComparer());

            // Execute activities to generate volatile transactions and balances
            foreach (Activity activity in Activities) 
            {
                activity.Execute();                
            }
        }

        public void AddVolatileActor(Actor actor)
        {
            VolatileActors.Add(actor);
        }

        #endregion

        #region Filtering

        public Fund FindFund(string FundName)
        {
            //return Funds.Find(delegate(Fund f) { return f.Name == FundName; });
            return Funds.First(f => f.Name == FundName);
        }

        public Actor FindActor(string ActorName)
        {
            //return Actors.Find(delegate(Actor a) { return a.ActorName == ActorName; });
            return Actors.First(a => a.ActorName == ActorName);
        }

        // Filter Activity List by Fund
        // This will pull in both Actor and FundReconciles which affect the fund passed.
        public List<Activity> ActivitiesByFund(Fund fund)
        {
            List<Activity> filtered = new List<Activity>();

            foreach (Activity activity in Activities)
            {
                if (activity.DoesAffectFund(fund)) filtered.Add(activity);
            }

            return filtered;
        }

        public List<Activity> ActivitiesInRange(DateTime StartDate, DateTime EndDate)
        {
            List<Activity> filtered = new List<Activity>();

            foreach (Activity activity in Activities)
            {
                if (activity.ActivityDate >= StartDate && activity.ActivityDate <= EndDate)
                {
                    // Hack to prevent Volatile Actors from showing up in Upcoming Activities
                    if (!(activity.ActivityType == ActivityTypeEnum.Actor && VolatileActors.Contains(activity.AffectingActor)) ) {
                        filtered.Add(activity);
                    }
                }
            }

            return filtered;
        }

        // GetValueFunds() : is used by StackedValue graph
        public List<Fund> GetValueFunds()
        {
            List<Fund> filtered = new List<Fund>();

            foreach (Fund f in Funds)
            {
                if (f.FundClass == FundClassEnum.Savings || f.FundClass == FundClassEnum.DebtEquity)
                {
                    if (!f.IsHidden && !f.IsBenchmark) filtered.Add(f);
                }
            }

            return filtered;
        }

        public List<Fund> GetDebtFunds()
        {
            List<Fund> filtered = new List<Fund>();

            foreach (Fund f in Funds)
            {
                if (f.FundClass == FundClassEnum.Debt || f.FundClass == FundClassEnum.DebtEquity)
                {
                    if (!f.IsHidden && !f.IsBenchmark) filtered.Add(f);
                }
            }

            return filtered;
        }

        public List<Fund> GetFundsOfClass(FundClassEnum fundclass)
        {
            List<Fund> filtered = new List<Fund>();

            foreach (Fund f in Funds)
            {
                if (f.FundClass == fundclass) filtered.Add(f);
            }

            return filtered;
        }

        #endregion

        #region Analysis

        // Created to allow calculation of beginning equity 
        public Decimal CalcEquityInitial()
        {
            Decimal initialequity = 0.0m;
            DateTime FirstDate = DateTime.MinValue;

            foreach (Activity activity in Activities)
            {
                if (FirstDate != DateTime.MinValue && activity.ActivityDate > FirstDate) break;

                if (activity.ActivityType == ActivityTypeEnum.Actor)
                {
                    if (activity.AffectingActor.ActorName.Contains("Projection Start"))
                    {
                        Fund f = FindFund(activity.AffectingActor.ActorFundPrimary);

                        if (!f.IsBenchmark && f.FundClass == FundClassEnum.DebtEquity && activity.RenderedBalancePrimary != null)
                        {
                            initialequity += (f.InceptionBalance.Amount + f.EquityOffset
                                - activity.RenderedBalancePrimary.Amount);
                            FirstDate = activity.ActivityDate;
                        }
                    }
                }
            }

            return initialequity;
        }

        public Decimal CalculateFundDelta(Fund fund, DateTime StartDate)
        {
            Decimal Result = 0.0m;

            if (fund.FundClass == FundClassEnum.SpendingAccount)
            {
                foreach (Transaction trans in fund.VolatileTransactions)
                {
                    if (trans.BalanceDate >= StartDate) Result += trans.Amount;
                }
            }
            else
            {
                Balance balstart = fund.CalculateBalance(StartDate);
                Balance balend = fund.InterimBalance;
                Result = balend.Amount - balstart.Amount;
            }

            return Result;
        }

        public Decimal CalculateFundClassDelta(FundClassEnum FundClass, DateTime StartDate)
        {
            Decimal delta = 0.0m;

            foreach (Fund f in Funds)
            {
                if (f.FundClass == FundClass && !f.IsBenchmark) delta += CalculateFundDelta(f, StartDate);
            }

            return delta;
        }

        public Decimal CalculateInterestOverhead(DateTime StartDate)
        {
            Decimal InterestOverhead = 0.0m;

            foreach (Fund f in Funds)
            {
                // The Spending Account keeps track of Interest Payments, the loan account only tracks principle
                if (!f.IsBenchmark && f.FundClass == FundClassEnum.SpendingAccount)
                {
                    foreach (Transaction t in f.VolatileTransactions)
                    {
                        if (t.BalanceDate >= StartDate && t.Description.Contains("(Overhead)"))
                        {
                            InterestOverhead += Math.Abs(t.Amount);
                        }
                    }
                }
            }

            return InterestOverhead;
        }

        #endregion
    }
}
